/*
 * Copyright 2009 Biz-e (info@biz-e.nl)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.biz_e.gwtappengine.lib.client;

import java.util.List;
import java.util.Vector;

import nl.biz_e.appengine.lib.client.login.GoogleLogin;
import nl.biz_e.appengine.lib.client.login.GoogleLogin.GoogleLoginListener;
import nl.biz_e.appengine.lib.shared.login.GoogleLoginInfo;
import nl.biz_e.appengine.lib.shared.login.IAuthenticator;
import nl.biz_e.appengine.lib.shared.login.IAuthorisationContext;
import nl.biz_e.appengine.lib.shared.login.IUser;
import nl.biz_e.gwt.baselib.ui.client.menu.IMenuItem;
import nl.biz_e.gwt.baselib.ui.client.menu.MenuPanelManager;
import nl.biz_e.gwt.baselib.ui.client.panels.BizeFooter;
import nl.biz_e.gwt.baselib.ui.client.panels.BusyPanel;
import nl.biz_e.gwt.baselib.ui.client.panels.IAppPanel;
import nl.biz_e.gwt.baselib.ui.client.panels.LoginErrorPanel;
import nl.biz_e.gwt.baselib.ui.client.panels.PanelManager;
import nl.biz_e.gwt.baselib.ui.client.panels.PreLoginPanel;
import nl.biz_e.gwt.baselib.ui.client.windows.MessageBox;
import nl.biz_e.gwtappengine.lib.client.panels.IAppEngineAppManager;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;

/**
 * 
 * @author Joost A. Bloemsma
 */
public abstract class AbstractApplication implements EntryPoint,
		IAppEngineAppManager, GoogleLoginListener {
	private final String appName;
	private MenuPanelManager menu;
	private PanelManager pnlMgr;
	private GoogleLoginInfo loginInf = null;
	private final Vector<IMenuItem> menuItems = new Vector<IMenuItem>();
	private boolean useGoogleLogin = true;
	private Messages messages = (Messages) GWT.create(Messages.class);
	private PopupPanel busyPanel;
	private String urlExt = "";
	private boolean showPreLoginPanel = true;
	private IAuthenticator authenticator;
	
	/**
	 * Constructs a new AbstractApplication instance
	 * 
	 * @param appName
	 */
	public AbstractApplication(String appName) {
		this(appName, true);
	}
	
	/**
	 * Constructs a new AbstractApplication instance
	 * 
	 * @param appName
	 * @param useFooter
	 */
	public AbstractApplication(String appName, boolean useFooter) {
		this.appName = appName;
		pnlMgr = new PanelManager(this);
		if (useFooter) {
			RootPanel.get().add(new BizeFooter());
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void onModuleLoad() {
		Log.setUncaughtExceptionHandler();
		Log.debug("onModuleLoad called");
		DeferredCommand.addCommand(new Command() {
			
			@Override
			public void execute() {
				onModuleLoad2();
			}
		});
	}
	
	private void onModuleLoad2() {
		menu = pnlMgr.getMenuManager();
		menu.init();
		this.login();
	}
	
	/**
	 * Starts the authorisation process.<br/>
	 * Is called deferred from onModuleLoad. Can be overridden to bypass
	 * authorisation.
	 */
	protected void login() {
		if (useGoogleLogin) {
			Log.debug("Using GoogleLogin");
			GoogleLogin.login(this, urlExt);
		} else {
			Log.debug("Not using GoogleLogin. Starting app.");
			this.onStart();
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getAppName() {
		return this.appName;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<IMenuItem> getMenuItems() {
		return this.menuItems;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setPanelManager(PanelManager panelManager) {
		this.pnlMgr = panelManager;
		pnlMgr.getMenuManager().addMenuItems(menuItems);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onLoginEvent(GoogleLoginInfo loginInfo) {
		if (loginInfo != null) {
			if (loginInfo.isLoggedIn()) {
				if (!this.isLoginAuthorized(loginInfo)) {
					RequestBuilder builder = new RequestBuilder(
							RequestBuilder.GET, loginInfo.getLogoutUrl());
					builder.setCallback(new RequestCallback() {
						
						@Override
						public void onResponseReceived(Request request,
								Response response) {
							Log.debug("Logout succeeded");
						}
						
						@Override
						public void onError(Request request, Throwable exception) {
							Log.error("Error logging out", exception);
						}
					});
					try {
						builder.send();
					} catch (RequestException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
					loginInfo.setLoginError(true);
					loginInfo.setLoggedIn(false);
				}
			}
		}
		this.loginInf = loginInfo;
		if (loginInf.isLoggedIn()) {
			menu.setUserName(loginInf.getNickname());
			menu.setLogOutUrl(loginInf.getLogoutUrl());
			onLoggedIn();
		} else if (loginInf.isLoginError()) {
			menu.setLogInUrl(loginInf.getLoginUrl());
			onLoginError();
		} else {
			menu.setLogInUrl(loginInf.getLoginUrl());
			onLoggedOut();
		}
		menu.setLoggedIn(loginInf.isLoggedIn());
	}
	
	/**
	 * Is triggered when a user logs in.<br/>
	 * Information can be obtained using {@link #getLoginInfo()}
	 */
	public void onLoggedIn() {
		// To be implemented at subclass level when using Google Login
	}
	
	/**
	 * Uses the {@link IAuthenticator} instance to authenticate
	 * the user using the app as the context. If no authenticator has been
	 * registered, false is returned.<br/>
	 * So if you don't want to use an authenticator, override this method.
	 * 
	 * @param loginInfo
	 * @return true if the user is authorized for this app
	 */
	protected boolean isLoginAuthorized(GoogleLoginInfo loginInfo) {
		if (this.getAuthenticator() != null) {
			return this.getAuthenticator().isAuthorized(loginInfo, this);
		} else {
			return false;
		}
	}
	
	/**
	 * Is fired when the application is started without using Google Login
	 */
	public void onStart() {
		// To be implemented at subclass level when NOT using Google Login
	}
	
	/**
	 * Is triggered when a user logs off, or the application is started without
	 * a valid session<br/>
	 * If not overridden, opens the default {@link PreLoginPanel} or redirects
	 * to the Google login page. This depends on the value of showPreLoginPanel.
	 */
	public void onLoggedOut() {
		if (this.getShowPreLoginPanel()) {
			this.showPanel(new PreLoginPanel(this.getAppName()));
		} else {
			Window.open(this.getLoginInfo().getLoginUrl(), "_self", "");
		}
	}
	
	/**
	 * Is triggered when a login error occures (eg wrong username/pwd.<br/>
	 * If not overridden, opens the default {@link LoginErrorPanel}
	 */
	public void onLoginError() {
		this.showPanel(new LoginErrorPanel(this.getAppName()));
	}
	
	/**
	 * Adds an {@link IMenuItem} implementation
	 * 
	 * @param menuItem
	 */
	public void addMenuItem(IMenuItem menuItem) {
		if (menuItem == null)
			return;
		menuItems.add(menuItem);
	}
	
	/**
	 * Removes an {@link IMenuItem} implementation
	 * 
	 * @param menuItem
	 */
	public void removeMenuItem(IMenuItem menuItem) {
		if (menuItem == null)
			return;
		menuItems.remove(menuItem);
	}
	
	/**
	 * Returns the current loginInfo
	 * 
	 * @return {@link GoogleLoginInfo} object
	 */
	public GoogleLoginInfo getLoginInfo() {
		return this.loginInf;
	}
	
	/**
	 * Just a convenience method for {@link PanelManager#showPanel(IAppPanel)}
	 * 
	 * @param panel
	 */
	protected void showPanel(IAppPanel panel) {
		panel.setPanelManager(pnlMgr);
		pnlMgr.showPanel(panel);
	}
	
	/**
	 * @param useGoogleLogin
	 *        the useGoogleLogin to set
	 */
	public void setUseGoogleLogin(boolean useGoogleLogin) {
		this.setUseGoogleLogin(useGoogleLogin, "");
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void setUseGoogleLogin(boolean useGoogleLogin, String urlExt) {
		this.useGoogleLogin = useGoogleLogin;
		if (urlExt == null)
			urlExt = "";
		this.urlExt = urlExt;
	}
	
	/**
	 * @return the useGoogleLogin
	 */
	public boolean useGoogleLogin() {
		return useGoogleLogin;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public IMenuItem getLoginItem() {
		return null;
	}
	
	/**
	 * @return the pnlMgr
	 */
	public PanelManager getPanelManager() {
		return pnlMgr;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void showMessage(String msg) {
		new MessageBox(msg).show();
	}
	
	/**
	 * @param busyPanel
	 */
	public void setBusyPanel(PopupPanel busyPanel) {
		this.busyPanel = busyPanel;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void showBusy() {
		if (busyPanel == null) {
			busyPanel = new BusyPanel();
		}
		busyPanel.show();
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void hideBusy() {
		if (busyPanel != null) {
			busyPanel.hide();
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Messages getMessages() {
		return messages;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getName() {
		return this.getAppName();
	}
	
	/**
	 * @return the showPreLoginPanel
	 */
	protected boolean getShowPreLoginPanel() {
		return showPreLoginPanel;
	}
	
	/**
	 * Set to {@code false} to bypass the welcoming preLoginPanel.
	 * 
	 * @param showPreLoginPanel
	 *        the showPreLoginPanel to set
	 */
	protected void setShowPreLoginPanel(boolean showPreLoginPanel) {
		this.showPreLoginPanel = showPreLoginPanel;
	}
	
	/**
	 * @return the authenticator
	 */
	public IAuthenticator getAuthenticator() {
		if (authenticator == null) {
			Log.warn("No authenticator defined. Using test authenticator (all allowed)");
			this.setAuthenticator(new IAuthenticator() {
				@Override
				public boolean isAuthorized(IUser user, IAuthorisationContext context) {
					return true;
				}
			});
		}
		return authenticator;
	}
	
	/**
	 * @param authenticator
	 *        the authenticator to set
	 */
	public void setAuthenticator(IAuthenticator authenticator) {
		this.authenticator = authenticator;
	}
}
